package lg.minecraft.plugin.cobblemoncore.util;

import com.cobblemon.mod.common.Cobblemon;
import com.cobblemon.mod.common.api.pokemon.stats.Stat;
import com.cobblemon.mod.common.api.pokemon.stats.Stats;
import com.cobblemon.mod.common.api.pokemon.status.Status;
import com.cobblemon.mod.common.api.pokemon.status.Statuses;
import com.cobblemon.mod.common.api.properties.CustomPokemonProperty;
import com.cobblemon.mod.common.api.types.tera.TeraTypes;
import kotlin.jvm.internal.Intrinsics;
import org.jetbrains.annotations.NotNull;

import java.util.*;
import java.util.stream.Collectors;

/**
 * @author langle__
 * @version 1.0
 */
public class PokemonPropertyParamsSuggester {

    public static final PokemonPropertyParamsSuggester INSTANCE = new PokemonPropertyParamsSuggester();

    private final Set<SuggestionHolder> providers = new HashSet<>();

    public PokemonPropertyParamsSuggester() {
        load();
    }

    public final void load() {
        providers.clear();
        this.addDefaults();
        this.addExtra();
        this.addCustom();
    }

    public final void inject(@NotNull Iterable<String> keys, @NotNull Collection<String> suggestions) {
        List<String> list = new ArrayList<>();
        keys.forEach(list::add);
        providers.add(new SuggestionHolder(list, suggestions));
    }

    @NotNull
    public final List<SuggestionHolder> keys() {
        return new ArrayList<>(providers);
    }

    @NotNull
    public final List<String> suggestKeys(@NotNull String partialKey, @NotNull Collection<String> excludedKeys) {
        List<String> list = new ArrayList<>();
        providers.forEach(provider -> {
            boolean noneExcluded = provider.keys.stream().noneMatch(excludedKeys::contains);
            if (noneExcluded) {
                provider.keys.stream().filter(key -> key.contains(partialKey)).forEach(list::add);
            }
        });
        return list;
    }

    @NotNull
    public final List<String> suggestValues(@NotNull String possibleKey, @NotNull String currentValue) {
        SuggestionHolder suggestionHolder = null;
        for (SuggestionHolder provider : providers) {
            if (provider.keys.contains(possibleKey)) {
                suggestionHolder = provider;
                break;
            }
        }
        if (suggestionHolder == null) return List.of();
        String newValue = currentValue;
        String legacy = "";
        if (possibleKey.equals("move") && currentValue.contains(",")) {
            int lastIndex = currentValue.lastIndexOf(",");
            newValue = currentValue.substring(lastIndex + 1);
            legacy = currentValue.substring(0, lastIndex + 1);
        }
        List<String> list = new ArrayList<>();
        for (String suggestion : suggestionHolder.suggestions) {
            if (suggestion.contains(newValue)) {
                if (possibleKey.equals("move")) {
                    list.add(possibleKey + "=" + legacy + suggestion);
                    continue;
                }
                list.add(possibleKey + "=" + suggestion);
            }
        }
        return list;
    }

    private void addDefaults() {
        List<String> teraList = new ArrayList<>();
        TeraTypes instance = TeraTypes.INSTANCE;
        instance.forEach(teraType -> teraList.add(teraType.getId().toString()));

        this.inject(List.of("level", "lvl", "l"), List.of("1", String.valueOf(Cobblemon.INSTANCE.getConfig().getMaxPokemonLevel())));
        this.inject(List.of("shiny", "s"), List.of("yes", "no"));
        this.inject(List.of("gender"), PokemonDataCache.getInstance().getGenderKeySet());
        this.inject(List.of("friendship"), List.of("0", String.valueOf(Cobblemon.INSTANCE.getConfig().getMaxPokemonFriendship())));
        this.inject(List.of("pokeball"), PokemonDataCache.getInstance().getPokeBallKeySet());
        this.inject(List.of("nature"), PokemonDataCache.getInstance().getNatureKeySet());
        this.inject(List.of("ability"), PokemonDataCache.getInstance().getAbilityKeySet());
        this.inject(List.of("dmax"), List.of("0", String.valueOf(Cobblemon.INSTANCE.getConfig().getMaxDynamaxLevel())));
        this.inject(List.of("gmax"), List.of("yes", "no"));
        this.inject(List.of("type", "elemental_type"), PokemonDataCache.getInstance().getElementalTypeKeySet());
        this.inject(List.of("tera_type", "tera"), teraList);
        this.inject(List.of("tradeable"), List.of("yes", "no"));
        this.inject(List.of("originaltrainer", "ot"), List.of(""));
        this.inject(List.of("originaltrainertype", "ottype"), List.of("None", "Player", "NPC"));
        this.inject(List.of("nickname", "nick"), List.of(""));
        this.inject(List.of("helditem", "held_item"), List.of("cobblemon:exp_share"));

        for (Stat stat : Stats.Companion.getPERMANENT()) {
            String statName = stat.toString().toLowerCase(Locale.ROOT);
            this.inject(List.of(statName + "_iv"), List.of("0", "31"));
            this.inject(List.of(statName + "_ev"), List.of("0", "252"));
        }

        List<Status> statusList = Statuses.INSTANCE.getPersistentStatuses();
        this.inject(List.of("status"), statusList.stream().map(status -> status.getName().toString()).collect(Collectors.toList()));
    }

    private void addExtra() {
        this.inject(List.of("move"), PokemonDataCache.getInstance().getMoveKeySet());
    }

    private void addCustom() {
        CustomPokemonProperty.Companion.getProperties().forEach(property -> this.inject(property.getKeys(), property.examples()));
    }

    public record SuggestionHolder(@NotNull Collection<String> keys, @NotNull Collection<String> suggestions) {

        @Override
        public String toString() {
            return "SuggestionHolder{" +
                    "keys=" + keys +
                    ", suggestions=" + suggestions +
                    '}';
        }

        @Override
        public boolean equals(Object other) {
            if (this == other) {
                return true;
            } else if (!(other instanceof SuggestionHolder var2)) {
                return false;
            } else {
                if (!Intrinsics.areEqual(this.keys, var2.keys)) {
                    return false;
                } else {
                    return Intrinsics.areEqual(this.suggestions, var2.suggestions);
                }
            }
        }

    }

}
